/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments.records.restore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.model.AttributeElement;
import org.unidata.mdm.core.type.timeline.TimeInterval;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.data.context.RecordIdentityContextSupport;
import org.unidata.mdm.data.context.RestoreRecordRequestContext;
import org.unidata.mdm.data.exception.DataConsistencyException;
import org.unidata.mdm.data.exception.DataExceptionIds;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.impl.RecordValidationComponent;
import org.unidata.mdm.data.type.data.EtalonRecord;
import org.unidata.mdm.data.type.data.OriginRecord;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.system.exception.ValidationResult;
import org.unidata.mdm.system.type.pipeline.Point;
import org.unidata.mdm.system.type.pipeline.Start;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;

/**
 * @author Mikhail Mikhailov
 */
@Component(RecordRestoreValidateExecutor.SEGMENT_ID)
public class RecordRestoreValidateExecutor
        extends Point<RestoreRecordRequestContext>
        implements RecordIdentityContextSupport {
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RECORD_RESTORE_VALIDATE]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".record.restore.validate.description";
    /**
     * Record against meta model validation service.
     */
    @Autowired
    private RecordValidationComponent recordValidationComponent;
    /**
     * Constructor.
     */
    public RecordRestoreValidateExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void point(RestoreRecordRequestContext ctx) {

        MeasurementPoint.start();
        try {

            // Check resulting data consistency
            Timeline<OriginRecord> next = ctx.nextTimeline();
            if (Objects.isNull(next) || next.isEmpty() || ctx.isDataRestore()) {
                return;
            }

            Map<AttributeElement, List<Object>> collected = new HashMap<>();
            RecordKeys keys = ctx.keys();
            for (TimeInterval<OriginRecord> i : next) {

                EtalonRecord record = i.getCalculationResult();
                if (Objects.isNull(record)) {
                    return;
                }

                recordValidationComponent.checkRecordsDataRecord(record, keys.getEntityName());
                Map<AttributeElement, Collection<Object>> found = recordValidationComponent.checkMissedLookupValues(keys, record);

                found.entrySet().forEach(entry -> collected
                        .computeIfAbsent(entry.getKey(), k -> new ArrayList<Object>())
                        .addAll(entry.getValue()));
            }

            if (MapUtils.isNotEmpty(collected)) {

                List<ValidationResult> v = collected.entrySet().stream()
                    .map(entry -> new ValidationResult(
                            "Attribute [{}] references missing lookup code values [{}].",
                            "attribute.references.missing.code.values", entry.getKey().getName(), entry.getValue()))
                    .collect(Collectors.toList());

                throw new DataConsistencyException("Some attributes reference missing code values.",
                        DataExceptionIds.EX_DATA_RESTORE_MISSING_CODE_REFERENCED, v);
            }

        } finally {
            MeasurementPoint.stop();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(Start<?, ?> start) {
        return RestoreRecordRequestContext.class.isAssignableFrom(start.getInputTypeClass());
    }
}
